Skip to content

Introduce N4W1 backup/recovery flows#6799

Open
romanz wants to merge 4 commits intomainfrom
romanz/2604/n4w1-demo
Open

Introduce N4W1 backup/recovery flows#6799
romanz wants to merge 4 commits intomainfrom
romanz/2604/n4w1-demo

Conversation

@romanz
Copy link
Copy Markdown
Contributor

@romanz romanz commented Apr 20, 2026

Design: https://satoshilabs.slack.com/docs/T0J8V2YBY/F0AKE51PVFZ

Also add N4W1-enabled builds to the CI for integration tests.

I have used https://docs.pytest.org/en/stable/how-to/fixtures.html#parametrizing-fixtures in order to re-use some of the existing backup/recovery device tests in a separate CI job:

image

The rest of the test-related changes will be done in separate PRs.

@romanz romanz self-assigned this Apr 20, 2026
@trezor-bot trezor-bot Bot added this to Firmware Apr 20, 2026
@romanz romanz changed the base branch from main to romanz/2604/show-info April 20, 2026 14:02
@github-project-automation github-project-automation Bot moved this to 🔎 Needs review in Firmware Apr 20, 2026
@romanz romanz moved this from 🔎 Needs review to 🏃‍♀️ In progress in Firmware Apr 20, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 20, 2026

Walkthrough

Adds feature-gated N4W1 backup/recovery support across firmware and tests: CI matrix and artifact names gain an n4w1 dimension/suffix; firmware gains N4W1 read/write handlers, retry/progress/validation flows, and translation/QSTR entries for N4W1 error states; UI layouts expose an N4W1 backup selection; test infrastructure and input-flow helpers are parameterized to exercise BackupMethod.N4W1 alongside the existing Display flow.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant User as "User"
    participant UI as "Device UI"
    participant FW as "Firmware handler"
    participant Debug as "DebugLink/Emulator"
    participant Host as "Test harness / CI"

    User->>UI: select backup method (Display | N4W1)
    UI->>FW: start backup/reset with chosen method
    alt method == N4W1
        FW->>UI: prompt to scan/write N4W1 tag
        UI->>Debug: request backup_read / backup_write
        Debug-->>UI: deliver tag payload or status
        UI->>FW: forward payload/status
        FW->>FW: decode mnemonic, validate incrementally
        alt damaged/empty/non-empty
            FW->>UI: show n4w1__err_damaged / n4w1__err_empty / n4w1__err_nonempty
            UI->>User: request retry / new tag
            User->>UI: retry scan/write
        else valid
            FW->>UI: show progress & continue
            UI->>Host: report completion
        end
    else
        FW->>UI: perform Display flow (show/confirm words)
        UI->>Host: report completion
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested labels

core

Suggested reviewers

  • matejcik
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 3.66% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive The PR description provides a design reference and explains the test reuse strategy via pytest fixture parametrization, but lacks specific sections for PR setup, development status, and post-merge QA instructions as outlined in the repository template. Add PR setup details (assignment, project/priority/sprint setup), set development status (Draft/Needs Review), and include a 'Notes for QA' section with clear testing instructions if this PR requires QA validation.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Introduce N4W1 backup/recovery flows' accurately describes the main changes—adding N4W1 backup and recovery functionality throughout the codebase and CI configuration.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch romanz/2604/n4w1-demo

Warning

Review ran into problems

🔥 Problems

Timed out fetching pipeline failures after 30000ms


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
Review rate limit: 7/8 reviews remaining, refill in 7 minutes and 30 seconds.

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 20, 2026

en main(all)

model device_test click_test persistence_test
T2T1 test(all) main(all) test(all) main(all) test(all) main(all)
T3B1 test(all) main(all) test(all) main(all) test(all) main(all)
T3T1 test(all) main(all) test(all) main(all) test(all) main(all)
T3W1 test(all) main(all) test(all) main(all) test(all) main(all)
Translations

cs main(all)

model device_test click_test
T2T1 test(all) main(all) test(all) main(all)
T3B1 test(all) main(all) test(all) main(all)
T3T1 test(all) main(all) test(all) main(all)
T3W1 test(all) main(all) test(all) main(all)

de main(all)

model device_test click_test
T2T1 test(all) main(all) test(all) main(all)
T3B1 test(all) main(all) test(all) main(all)
T3T1 test(all) main(all) test(all) main(all)
T3W1 test(all) main(all) test(all) main(all)

es main(all)

model device_test click_test
T2T1 test(all) main(all) test(all) main(all)
T3B1 test(all) main(all) test(all) main(all)
T3T1 test(all) main(all) test(all) main(all)
T3W1 test(all) main(all) test(all) main(all)

fr main(all)

model device_test click_test
T2T1 test(all) main(all) test(all) main(all)
T3B1 test(all) main(all) test(all) main(all)
T3T1 test(all) main(all) test(all) main(all)
T3W1 test(all) main(all) test(all) main(all)

pt main(all)

model device_test click_test
T2T1 test(all) main(all) test(all) main(all)
T3B1 test(all) main(all) test(all) main(all)
T3T1 test(all) main(all) test(all) main(all)
T3W1 test(all) main(all) test(all) main(all)

Latest CI run: 25122548722

@romanz romanz force-pushed the romanz/2604/n4w1-demo branch from 4353da1 to 33e6532 Compare April 20, 2026 19:36
@romanz romanz force-pushed the romanz/2604/show-info branch from 099bfb0 to 14265bc Compare April 21, 2026 05:40
Base automatically changed from romanz/2604/show-info to main April 21, 2026 06:16
@romanz romanz force-pushed the romanz/2604/n4w1-demo branch 2 times, most recently from 8fe2388 to 94b032e Compare April 21, 2026 15:22
@romanz romanz changed the base branch from main to romanz/2604/n4w1-prep April 21, 2026 15:22
@romanz romanz mentioned this pull request Apr 21, 2026
@romanz romanz force-pushed the romanz/2604/n4w1-prep branch 2 times, most recently from ac4c601 to 1df8713 Compare April 22, 2026 07:01
@romanz romanz force-pushed the romanz/2604/n4w1-demo branch from 94b032e to 233d9b9 Compare April 22, 2026 07:01
Base automatically changed from romanz/2604/n4w1-prep to main April 24, 2026 17:33
@romanz romanz force-pushed the romanz/2604/n4w1-demo branch 11 times, most recently from 671ea4e to 9af64ff Compare April 29, 2026 10:04
@romanz romanz added the translations Put this label on a PR to run tests in all languages label Apr 29, 2026
@romanz romanz force-pushed the romanz/2604/n4w1-demo branch 3 times, most recently from 2c32f2f to 0429757 Compare April 29, 2026 12:22
@romanz romanz changed the title [WIP] Introduce N4W1 demo flows Introduce N4W1 backup/recovery flows Apr 29, 2026
@romanz romanz force-pushed the romanz/2604/n4w1-demo branch from 0429757 to c9b03d0 Compare April 29, 2026 12:28
@romanz romanz marked this pull request as ready for review April 29, 2026 13:44
@romanz romanz requested a review from OriginalEveres April 29, 2026 13:51
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🧹 Nitpick comments (1)
tests/input_flows.py (1)

2824-2828: Prefer explicit method allow-list in recovery constructors.

Line 2824 and Line 2855 currently treat any non-Display value as the “non-word-count path”. That works for N4W1, but it can silently accept unsupported future values. Fail fast with an explicit allow-list.

Suggested hardening diff
@@
-        self.method = method
-        if method is messages.BackupMethod.Display:
+        self.method = method
+        if method not in (
+            messages.BackupMethod.Display,
+            messages.BackupMethod.N4W1,
+        ):
+            raise ValueError(f"Unsupported backup method: {method!r}")
+        if method is messages.BackupMethod.Display:
             self.word_count = len(shares[0].split(" "))
         else:
             self.word_count = None
@@
-        self.method = method
-        if self.method is messages.BackupMethod.Display:
+        self.method = method
+        if self.method not in (
+            messages.BackupMethod.Display,
+            messages.BackupMethod.N4W1,
+        ):
+            raise ValueError(f"Unsupported backup method: {self.method!r}")
+        if self.method is messages.BackupMethod.Display:
             self.word_count = len(shares[0].split(" "))
         else:
             self.word_count = None

Also applies to: 2855-2859

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/input_flows.py` around lines 2824 - 2828, Replace the broad "else"
handling that treats any non-Display BackupMethod as the non-word-count path
with an explicit allow-list: in the blocks that check "if method is
messages.BackupMethod.Display" (and the similar block around lines 2855-2859),
explicitly branch for each supported enum value (e.g., "elif method is
messages.BackupMethod.<SUPPORTED_METHOD>" for methods that should set word_count
= None) and add a final "else: raise ValueError(f'Unsupported BackupMethod:
{method}')" to fail fast on unknown future values so the constructors (where
word_count is assigned) only accept known BackupMethod members.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/core.yml:
- Around line 180-183: The workflow's core_emu_arm job contains an invalid
exclude block referencing values not present in its matrix (exclude: - type:
normal, asan: asan), which breaks linting; remove the invalid exclude entries
(or replace them with valid combinations matching the matrix keys type:
[debuglink] and asan: [noasan]) so the exclude only refers to actual matrix
values for the core_emu_arm job.
- Around line 309-314: The upload-artifact name template omits the n4w1 flag so
jobs with n4w1=true collide with base matrix artifacts; update the
upload-artifact step (the one that currently produces names like
core-test-device-T3W1-universal-en-noasan) to include the n4w1 suffix the same
way the download-artifact step does by appending a conditional expression using
matrix.n4w1 (e.g. append a conditional "-n4w1" via a GitHub Actions expression
such as ${{ matrix.n4w1 && '-n4w1' || '' }} to the artifact name) so artifact
names become unique when n4w1 is set.

In `@core/src/apps/management/recovery_device/layout.py`:
- Line 338: Replace the hardcoded English literal raised in RetryRead with a
translation key lookup; instead of raise RetryRead("This tag is empty. Continue
to scan a different tag.") call the project's i18n helper (e.g. _(...) or
translate(...)) with a new key such as "recovery_device.empty_tag" and use that
returned string in RetryRead (keep the exception type RetryRead unchanged and
add the new translation key to the locale files).
- Around line 387-389: Wrap the blob.decode() call in a try/except
UnicodeDecodeError around the code that assigns share and calls self.check_words
so malformed bytes do not raise; on UnicodeDecodeError return a recoverable
validation error through the existing validation/error path (do not let the
exception propagate) — e.g., catch UnicodeDecodeError when decoding blob, and
call the module's validation helper (reuse the same error-return mechanism used
elsewhere) to report "tag payload is not valid UTF-8" instead of crashing; keep
the rest of the flow (the call to self.check_words(share)) unchanged for the
successful decode branch.

In `@core/src/apps/management/reset_device/layout.py`:
- Around line 269-275: The warning description currently passes a hardcoded
English string via exc.msg to trezorui_api.show_warning inside
raise_if_not_confirmed (see raise_if_not_confirmed and trezorui_api.show_warning
usage); replace that hardcoded text with a translation lookup from TR (add a new
key such as TR.reset_device__non_empty_n4w1_tag or use an existing appropriate
key) and pass TR.<key> as the description instead of exc.msg, and apply the same
change to the other occurrence around the retry block (~line 295) so both
warnings are localized.

In `@tests/device_tests/reset_recovery/test_recovery_slip39_basic.py`:
- Line 71: The pytest param id is incorrect: the test currently uses the string
"3of5-33" for a vector that is actually a 2-of-5 case; update the identifier to
"2of5-33" (replace the "3of5-33" literal used in the test parameter list in
test_recovery_slip39_basic.py) so test reports and failure messages correctly
reflect the 2-of-5 vector.

---

Nitpick comments:
In `@tests/input_flows.py`:
- Around line 2824-2828: Replace the broad "else" handling that treats any
non-Display BackupMethod as the non-word-count path with an explicit allow-list:
in the blocks that check "if method is messages.BackupMethod.Display" (and the
similar block around lines 2855-2859), explicitly branch for each supported enum
value (e.g., "elif method is messages.BackupMethod.<SUPPORTED_METHOD>" for
methods that should set word_count = None) and add a final "else: raise
ValueError(f'Unsupported BackupMethod: {method}')" to fail fast on unknown
future values so the constructors (where word_count is assigned) only accept
known BackupMethod members.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 2a5da0be-6fc9-422d-97df-3cac922efd84

📥 Commits

Reviewing files that changed from the base of the PR and between 993dbad and e5c7f6d.

📒 Files selected for processing (19)
  • .github/workflows/core.yml
  • core/src/apps/management/recovery_device/layout.py
  • core/src/apps/management/reset_device/layout.py
  • core/src/trezor/ui/layouts/eckhart/recovery.py
  • tests/conftest.py
  • tests/device_tests/reset_recovery/test_recovery_bip39_dryrun.py
  • tests/device_tests/reset_recovery/test_recovery_bip39_t2.py
  • tests/device_tests/reset_recovery/test_recovery_slip39_advanced.py
  • tests/device_tests/reset_recovery/test_recovery_slip39_advanced_dryrun.py
  • tests/device_tests/reset_recovery/test_recovery_slip39_basic.py
  • tests/device_tests/reset_recovery/test_recovery_slip39_basic_dryrun.py
  • tests/device_tests/reset_recovery/test_reset_recovery_bip39.py
  • tests/device_tests/reset_recovery/test_reset_recovery_slip39_advanced.py
  • tests/device_tests/reset_recovery/test_reset_recovery_slip39_basic.py
  • tests/device_tests/reset_recovery/test_reset_slip39_basic.py
  • tests/device_tests/test_repeated_backup.py
  • tests/input_flows.py
  • tests/input_flows_helpers.py
  • tests/ui_tests/fixtures.json

Comment thread .github/workflows/core.yml Outdated
Comment thread .github/workflows/core.yml
Comment thread core/src/apps/management/recovery_device/layout.py Outdated
Comment thread core/src/apps/management/recovery_device/layout.py Outdated
Comment thread core/src/apps/management/reset_device/layout.py
Comment thread tests/device_tests/reset_recovery/test_recovery_slip39_basic.py Outdated
@romanz romanz marked this pull request as draft April 29, 2026 14:01
@romanz romanz force-pushed the romanz/2604/n4w1-demo branch from 4b31e1f to 1f936e1 Compare April 29, 2026 14:53
@romanz romanz marked this pull request as ready for review April 29, 2026 15:16
@romanz romanz requested a review from matejcik as a code owner April 29, 2026 15:16
@romanz romanz marked this pull request as draft April 29, 2026 15:18
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
core/src/apps/management/reset_device/layout.py (1)

237-243: Finish the missing N4W1 intro/safety screens.

intro() is still empty and backup() skips the warning shown by _DisplayBackup, so this path drops straight into writing secret shares to tags. I'd close those TODOs before enabling the flow outside demo builds.

Do you want me to sketch a minimal version that reuses the existing backup intro/warning layouts?

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@core/src/apps/management/reset_device/layout.py` around lines 237 - 243, The
intro() and backup() stubs in ResetDevice.layout must present the same N4W1
intro and safety screens used elsewhere instead of being no-ops; implement
intro(self, num_of_words) to render the existing backup/introduction text/UI
(reuse the layout or components used by the prior backup flow), and update
backup(self, iter_shares) to invoke the existing _DisplayBackup
warning/confirmation sequence before proceeding to write shares (so the user
must acknowledge safety). Locate and call the same helper/layout functions or
components used by the current backup flow (e.g., the backup intro screen and
_DisplayBackup warning) inside ResetDevice.layout.intro and
ResetDevice.layout.backup, preserving the num_of_words and iter_shares
parameters.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@core/src/apps/management/recovery_device/layout.py`:
- Around line 283-290: The current warning in choose_handler logs "Unsupported
backup method: None" for the intentional default path; change the condition so
that the warning is only emitted for explicit unsupported methods, not when
method is None. Concretely, update the conditional that checks method (in
function choose_handler) to first ensure method is not None and not
BackupMethod.Display before entering the __debug__ logging branch and calling
trezor.log.warning, so None is treated as the silent placeholder.

---

Nitpick comments:
In `@core/src/apps/management/reset_device/layout.py`:
- Around line 237-243: The intro() and backup() stubs in ResetDevice.layout must
present the same N4W1 intro and safety screens used elsewhere instead of being
no-ops; implement intro(self, num_of_words) to render the existing
backup/introduction text/UI (reuse the layout or components used by the prior
backup flow), and update backup(self, iter_shares) to invoke the existing
_DisplayBackup warning/confirmation sequence before proceeding to write shares
(so the user must acknowledge safety). Locate and call the same helper/layout
functions or components used by the current backup flow (e.g., the backup intro
screen and _DisplayBackup warning) inside ResetDevice.layout.intro and
ResetDevice.layout.backup, preserving the num_of_words and iter_shares
parameters.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 758731a6-ed4f-4b4c-b9f9-5b278cb2b8c9

📥 Commits

Reviewing files that changed from the base of the PR and between e5c7f6d and 1f936e1.

⛔ Files ignored due to path filters (1)
  • core/embed/rust/src/translations/generated/translated_string.rs is excluded by !**/generated/**
📒 Files selected for processing (9)
  • .github/workflows/core.yml
  • core/embed/rust/librust_qstr.h
  • core/mocks/trezortranslate_keys.pyi
  • core/src/apps/management/recovery_device/layout.py
  • core/src/apps/management/reset_device/layout.py
  • core/translations/en.json
  • core/translations/order.json
  • core/translations/signatures.json
  • tests/device_tests/reset_recovery/test_recovery_slip39_basic.py
✅ Files skipped from review due to trivial changes (5)
  • core/embed/rust/librust_qstr.h
  • core/translations/signatures.json
  • core/translations/en.json
  • core/translations/order.json
  • core/mocks/trezortranslate_keys.pyi
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/device_tests/reset_recovery/test_recovery_slip39_basic.py

Comment thread core/src/apps/management/recovery_device/layout.py
@romanz romanz force-pushed the romanz/2604/n4w1-demo branch from 1f936e1 to c5d047b Compare April 29, 2026 15:43
@romanz romanz marked this pull request as ready for review April 29, 2026 15:45
@romanz romanz moved this from 🏃‍♀️ In progress to 🔎 Needs review in Firmware Apr 29, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
.github/workflows/core.yml (1)

305-356: ⚠️ Potential issue | 🟠 Major

Keep N4W1 coverage separate from the default T3W1 report.

This include row adds a second T3W1/universal/noasan binary with N4W1=1, but the coverage pipeline still reuses only the standard core-emu-T3W1-universal-debuglink-noasan build for .i sources. That mixes coverage from two different compiled variants and can skew or break the final T3W1 coverage report. Either skip upload-coverage for matrix.n4w1 for now, or make the coverage artifacts/report job variant-aware (-n4w1 sources + separate merge/reporting).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/core.yml around lines 305 - 356, The pipeline is mixing
coverage from the N4W1 variant into the standard T3W1 report because matrix.n4w1
produces a second core-emu artifact but upload-coverage always processes the
default build; either prevent uploading coverage for N4W1 by adding a condition
to the upload-coverage step (if: ${{ !matrix.n4w1 }}) or make the coverage flow
variant-aware by including the n4w1 suffix in the core-emu download/upload
artifact names (core-emu-${{ matrix.model }}-${{ matrix.coins }}-debuglink-${{
matrix.asan }}${{ matrix.n4w1 && '-n4w1' || '' }}) and updating the
upload-coverage job to merge/report per-variant artifacts instead of a single
default artifact.
♻️ Duplicate comments (1)
core/src/apps/management/recovery_device/layout.py (1)

283-291: ⚠️ Potential issue | 🟡 Minor

Don't warn on the intentional method=None path.

When USE_N4W1 is off, this still logs Unsupported backup method: None for the normal default flow. Limit the warning to explicit non-Display values.

Proposed fix
-        if method is not BackupMethod.Display and __debug__:
+        if method not in (None, BackupMethod.Display) and __debug__:
             from trezor import log

             log.warning(__name__, "Unsupported backup method: %s", method)

Based on learnings, method=None passed to choose_backup_method() is intentionally distinct from BackupMethod.Display, and debug-mode unsupported-method warnings should be suppressed for None as well as for BackupMethod.Display.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@core/src/apps/management/recovery_device/layout.py` around lines 283 - 291,
The warning in choose_handler currently triggers for method==None; change the
condition so the debug warning only runs when method is neither None nor
BackupMethod.Display: update the if to check "method is not None and method is
not BackupMethod.Display and __debug__" (keep the import of BackupMethod and
return of _DisplayHandler unchanged) so intentional None flows do not log an
unsupported-method warning.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@core/src/apps/management/reset_device/layout.py`:
- Around line 241-259: The branch in _backup_share misinterprets ShareInfo.index
as a global position; change backup to materialize iter_shares into a flattened
list (so you can compute global_index and total_shares across groups) and pass
those global values into _backup_share (or adjust _backup_share signature to
accept global_index and total_shares) so the description/button decision uses
global_index == 0 and global_index == total_shares - 1; update any callers
(e.g., slip39_advanced_show_and_confirm_shares) to rely on the flattened order
instead of group-local ShareInfo.index/num_of_shares.

In `@core/src/trezor/ui/layouts/eckhart/recovery.py`:
- Around line 224-236: The choose_method function currently indexes a two-item
tuple with the result of trezorui_api.select_word(), which can return 0..3 and
will raise IndexError for values >=2; replace the tuple lookup with an explicit
conditional mapping in choose_method: call trezorui_api.select_word(...) as
before, then if index == 0 return BackupMethod.N4W1, elif index == 1 return
BackupMethod.Display, elif index in (2, 3) return a safe default (e.g.,
BackupMethod.Display) or raise a clear ValueError for unexpected slots;
reference the choose_method function, trezorui_api.select_word, BackupMethod and
the TR.backup__type_* symbols when making the change.

---

Outside diff comments:
In @.github/workflows/core.yml:
- Around line 305-356: The pipeline is mixing coverage from the N4W1 variant
into the standard T3W1 report because matrix.n4w1 produces a second core-emu
artifact but upload-coverage always processes the default build; either prevent
uploading coverage for N4W1 by adding a condition to the upload-coverage step
(if: ${{ !matrix.n4w1 }}) or make the coverage flow variant-aware by including
the n4w1 suffix in the core-emu download/upload artifact names (core-emu-${{
matrix.model }}-${{ matrix.coins }}-debuglink-${{ matrix.asan }}${{ matrix.n4w1
&& '-n4w1' || '' }}) and updating the upload-coverage job to merge/report
per-variant artifacts instead of a single default artifact.

---

Duplicate comments:
In `@core/src/apps/management/recovery_device/layout.py`:
- Around line 283-291: The warning in choose_handler currently triggers for
method==None; change the condition so the debug warning only runs when method is
neither None nor BackupMethod.Display: update the if to check "method is not
None and method is not BackupMethod.Display and __debug__" (keep the import of
BackupMethod and return of _DisplayHandler unchanged) so intentional None flows
do not log an unsupported-method warning.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 71009308-ea25-4473-9716-294aed30b1e5

📥 Commits

Reviewing files that changed from the base of the PR and between 1f936e1 and c5d047b.

⛔ Files ignored due to path filters (1)
  • core/embed/rust/src/translations/generated/translated_string.rs is excluded by !**/generated/**
📒 Files selected for processing (24)
  • .github/workflows/core.yml
  • core/embed/rust/librust_qstr.h
  • core/mocks/trezortranslate_keys.pyi
  • core/src/apps/management/recovery_device/layout.py
  • core/src/apps/management/reset_device/layout.py
  • core/src/trezor/ui/layouts/eckhart/recovery.py
  • core/translations/en.json
  • core/translations/order.json
  • core/translations/signatures.json
  • tests/conftest.py
  • tests/device_tests/reset_recovery/test_recovery_bip39_dryrun.py
  • tests/device_tests/reset_recovery/test_recovery_bip39_t2.py
  • tests/device_tests/reset_recovery/test_recovery_slip39_advanced.py
  • tests/device_tests/reset_recovery/test_recovery_slip39_advanced_dryrun.py
  • tests/device_tests/reset_recovery/test_recovery_slip39_basic.py
  • tests/device_tests/reset_recovery/test_recovery_slip39_basic_dryrun.py
  • tests/device_tests/reset_recovery/test_reset_recovery_bip39.py
  • tests/device_tests/reset_recovery/test_reset_recovery_slip39_advanced.py
  • tests/device_tests/reset_recovery/test_reset_recovery_slip39_basic.py
  • tests/device_tests/reset_recovery/test_reset_slip39_basic.py
  • tests/device_tests/test_repeated_backup.py
  • tests/input_flows.py
  • tests/input_flows_helpers.py
  • tests/ui_tests/fixtures.json
✅ Files skipped from review due to trivial changes (6)
  • tests/device_tests/reset_recovery/test_recovery_bip39_t2.py
  • core/translations/signatures.json
  • core/translations/en.json
  • core/embed/rust/librust_qstr.h
  • core/mocks/trezortranslate_keys.pyi
  • core/translations/order.json
🚧 Files skipped from review as they are similar to previous changes (12)
  • tests/device_tests/reset_recovery/test_recovery_slip39_advanced_dryrun.py
  • tests/device_tests/reset_recovery/test_reset_recovery_slip39_advanced.py
  • tests/device_tests/reset_recovery/test_reset_recovery_bip39.py
  • tests/device_tests/reset_recovery/test_recovery_bip39_dryrun.py
  • tests/device_tests/reset_recovery/test_reset_recovery_slip39_basic.py
  • tests/device_tests/reset_recovery/test_recovery_slip39_advanced.py
  • tests/device_tests/reset_recovery/test_reset_slip39_basic.py
  • tests/device_tests/reset_recovery/test_recovery_slip39_basic.py
  • tests/input_flows_helpers.py
  • tests/device_tests/test_repeated_backup.py
  • tests/device_tests/reset_recovery/test_recovery_slip39_basic_dryrun.py
  • tests/input_flows.py

Comment thread core/src/apps/management/reset_device/layout.py
Comment thread core/src/trezor/ui/layouts/eckhart/recovery.py
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
.github/workflows/core.yml (1)

334-339: Don't silently drop UI checks on the N4W1 universal/noasan path.

For this matrix entry ACTIONS_DO_UI_TEST is still true, but the N4W1 branch switches to test_emu_multicore instead of test_emu_ui_multicore. That loses the --ui=test --ui-check-missing --do-master-diff coverage the standard path gets for the same job shape.

Possible fix
       - name: Run device tests (N4W1)
         if: ${{ matrix.n4w1 }}  # TODO: test N4W1 UI fixtures as well
-        run: nix-shell --run "uv run make -C core ${{ env.ACTIONS_DO_UI_TEST == 'true' && 'test_emu_multicore' || 'test_emu' }}"
+        run: nix-shell --run "uv run make -C core ${{ env.ACTIONS_DO_UI_TEST == 'true' && 'test_emu_ui_multicore' || 'test_emu' }}"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/core.yml around lines 334 - 339, The N4W1 matrix branch
incorrectly uses test_emu_multicore instead of the UI-aware target, which drops
UI checks when ACTIONS_DO_UI_TEST is true; update the N4W1 run line to mirror
the non-N4W1 conditional expression so it selects test_emu_ui_multicore when
ACTIONS_DO_UI_TEST is 'true' (i.e., use the same ternary logic with
ACTIONS_DO_UI_TEST -> 'test_emu_ui_multicore' || 'test_emu' instead of switching
to 'test_emu_multicore'), ensuring the UI flags (--ui=test, --ui-check-missing,
--do-master-diff) are preserved for N4W1 runs.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In @.github/workflows/core.yml:
- Around line 334-339: The N4W1 matrix branch incorrectly uses
test_emu_multicore instead of the UI-aware target, which drops UI checks when
ACTIONS_DO_UI_TEST is true; update the N4W1 run line to mirror the non-N4W1
conditional expression so it selects test_emu_ui_multicore when
ACTIONS_DO_UI_TEST is 'true' (i.e., use the same ternary logic with
ACTIONS_DO_UI_TEST -> 'test_emu_ui_multicore' || 'test_emu' instead of switching
to 'test_emu_multicore'), ensuring the UI flags (--ui=test, --ui-check-missing,
--do-master-diff) are preserved for N4W1 runs.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ab51a135-d1d8-47a2-b5d1-53e25c5308bb

📥 Commits

Reviewing files that changed from the base of the PR and between c5d047b and b8e841b.

📒 Files selected for processing (1)
  • .github/workflows/core.yml

romanz added 2 commits April 29, 2026 18:24
Can be manually tested by invoking:
```
$ core/emu.py -ea -c trezorctl device setup -b shamir   # will run multi-share backup
$ core/tools/n1w1-emu.py run 127.0.0.1:21325 /tmp/tagN  # simulate tag connection and I/O
```

Enabled N4W1-based backup/recovery device tests for SLIP-39 single group scenarios.
Other device & click tests will be added in subsequent PRs.

[no changelog]
@romanz romanz force-pushed the romanz/2604/n4w1-demo branch from b8e841b to 53dcb05 Compare April 29, 2026 17:00
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (2)
core/src/trezor/ui/layouts/eckhart/recovery.py (1)

224-236: ⚠️ Potential issue | 🟠 Major

Handle unexpected select_word() values explicitly.

This still relies on a 2-item tuple lookup, so any unexpected index turns into a generic IndexError. Given the UI contract here, it should fail deliberately and readably instead.

Proposed fix
         index = await interact(
             trezorui_api.select_word(
                 title=title,
                 description=description,
                 words=(TR.backup__type_n4w1, TR.backup__type_wordlist, ""),
             ),
             br_name="choose_method",
         )
-        return (BackupMethod.N4W1, BackupMethod.Display)[index]
+        if index == 0:
+            return BackupMethod.N4W1
+        if index == 1:
+            return BackupMethod.Display
+        raise ValueError(f"Unexpected select_word index: {index}")

Based on learnings, trezorui_api.select_word() called from choose_method will only return 0 or 1 on Eckhart because the empty third word slot is disabled at the UI level. Unexpected return values (index >= 2) should trigger an explicit failure (e.g., raise ValueError) rather than silently defaulting to BackupMethod.Display.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@core/src/trezor/ui/layouts/eckhart/recovery.py` around lines 224 - 236, The
choose_method function uses the index from trezorui_api.select_word to pick from
a two-item tuple, which will raise a generic IndexError for unexpected values;
change choose_method (the async function calling trezorui_api.select_word) to
validate the returned index explicitly: accept 0 -> BackupMethod.N4W1, 1 ->
BackupMethod.Display, and if index is any other value raise a clear ValueError
(or custom error) with a descriptive message referencing the unexpected index
and the select_word call so failures are deliberate and readable.
core/src/apps/management/recovery_device/layout.py (1)

283-290: ⚠️ Potential issue | 🟡 Minor

Suppress the debug warning for method=None here as well.

None is the intentional default path, so this branch still logs Unsupported backup method: None on ordinary recovery calls when USE_N4W1 is off.

Proposed fix
-        if method is not BackupMethod.Display and __debug__:
+        if method not in (None, BackupMethod.Display) and __debug__:
             from trezor import log
 
             log.warning(__name__, "Unsupported backup method: %s", method)

Based on learnings, method=None passed to choose_backup_method() is intentionally distinct from BackupMethod.Display. None is reserved for a future flow where the user is prompted to choose between supported backup methods interactively. It should NOT be treated as equivalent to BackupMethod.Display, and debug-mode unsupported-method warnings should be suppressed for None (as well as for BackupMethod.Display).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@core/src/apps/management/recovery_device/layout.py` around lines 283 - 290,
The debug warning in choose_handler currently fires for method=None; update the
condition so it only logs when method is neither None nor BackupMethod.Display
(i.e., skip logging for the intentional default None path as well as the Display
enum). Modify the branch that checks BackupMethod to require method is not None
and method is not BackupMethod.Display before calling log.warning (refer to
function choose_handler, symbol BackupMethod, __debug__, and log.warning).
🧹 Nitpick comments (2)
.github/workflows/core.yml (1)

55-69: Consider adding a btconly N4W1 lane too.

All new N4W1 matrix rows are coins: universal, so CI still won’t build or exercise the btconly variant. If N4W1 is expected to ship on both firmware types, this leaves a blind spot for btconly-only regressions.

Based on learnings, when advertising the Capability.N4W1 capability, reviewers should ensure it is added for all firmware types (both Bitcoin-only and universal builds). Do not wrap f.capabilities.append(Capability.N4W1) with utils.BITCOIN_ONLY. The only acceptable conditional guard is utils.USE_N4W1.

Also applies to: 133-138, 306-311

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/core.yml around lines 55 - 69, The CI matrix is missing a
btconly N4W1 lane and some code conditionally guards N4W1 with
utils.BITCOIN_ONLY; add a matrix row mirroring the existing N4W1 universal entry
but with coins: btconly and n4w1: true (e.g., alongside the T3W1 n4w1 entry) so
btconly builds are exercised, and remove any conditional guards that append
Capability.N4W1 under utils.BITCOIN_ONLY — ensure Capability.N4W1 is only gated
by utils.USE_N4W1; apply the same change to the other similar blocks that add
N4W1 (the other matrix sections and any code paths that call
f.capabilities.append(Capability.N4W1)).
tests/input_flows.py (1)

2227-2242: Add an explicit assert for method validation to match InputFlowSlip39BasicBackup.

In InputFlowSlip39BasicBackup.input_flow_eckhart, there's an explicit assert self.method in (Display, N4W1). Here, the dict lookup implicitly validates valid methods, but an invalid method would raise a cryptic KeyError instead of a clear AssertionError.

♻️ Suggested fix for consistency
     def input_flow_eckhart(self) -> BRGeneratorType:
+        assert self.method in (
+            messages.BackupMethod.Display,
+            messages.BackupMethod.N4W1,
+        )
         num_screens = {
             messages.BackupMethod.Display: 10,
             messages.BackupMethod.N4W1: 8,
         }[self.method]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/input_flows.py` around lines 2227 - 2242, Add an explicit assertion to
validate self.method at the start of input_flow_eckhart so it mirrors
InputFlowSlip39BasicBackup: assert self.method in
(messages.BackupMethod.Display, messages.BackupMethod.N4W1). This makes method
validation explicit and raises an AssertionError with a clear failure mode
before using the dict lookup that currently raises a KeyError; place the assert
immediately before the num_screens assignment in the input_flow_eckhart method.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@core/src/apps/management/reset_device/layout.py`:
- Around line 237-246: The intro() method is currently a no-op and backup()
immediately writes shares, skipping the pre-export safety screens; implement
intro() to reuse the existing _DisplayBackup intro/warning flow (e.g., await
self._display_backup.intro(num_of_words) or delegate to the _DisplayBackup.intro
method) and ensure backup() calls await self.intro(num_of_words) (or awaits the
same _DisplayBackup warning) before iterating and calling _backup_share(share)
so the N4W1 path shows the same safety/acknowledgement screens as
_DisplayBackup.

---

Duplicate comments:
In `@core/src/apps/management/recovery_device/layout.py`:
- Around line 283-290: The debug warning in choose_handler currently fires for
method=None; update the condition so it only logs when method is neither None
nor BackupMethod.Display (i.e., skip logging for the intentional default None
path as well as the Display enum). Modify the branch that checks BackupMethod to
require method is not None and method is not BackupMethod.Display before calling
log.warning (refer to function choose_handler, symbol BackupMethod, __debug__,
and log.warning).

In `@core/src/trezor/ui/layouts/eckhart/recovery.py`:
- Around line 224-236: The choose_method function uses the index from
trezorui_api.select_word to pick from a two-item tuple, which will raise a
generic IndexError for unexpected values; change choose_method (the async
function calling trezorui_api.select_word) to validate the returned index
explicitly: accept 0 -> BackupMethod.N4W1, 1 -> BackupMethod.Display, and if
index is any other value raise a clear ValueError (or custom error) with a
descriptive message referencing the unexpected index and the select_word call so
failures are deliberate and readable.

---

Nitpick comments:
In @.github/workflows/core.yml:
- Around line 55-69: The CI matrix is missing a btconly N4W1 lane and some code
conditionally guards N4W1 with utils.BITCOIN_ONLY; add a matrix row mirroring
the existing N4W1 universal entry but with coins: btconly and n4w1: true (e.g.,
alongside the T3W1 n4w1 entry) so btconly builds are exercised, and remove any
conditional guards that append Capability.N4W1 under utils.BITCOIN_ONLY — ensure
Capability.N4W1 is only gated by utils.USE_N4W1; apply the same change to the
other similar blocks that add N4W1 (the other matrix sections and any code paths
that call f.capabilities.append(Capability.N4W1)).

In `@tests/input_flows.py`:
- Around line 2227-2242: Add an explicit assertion to validate self.method at
the start of input_flow_eckhart so it mirrors InputFlowSlip39BasicBackup: assert
self.method in (messages.BackupMethod.Display, messages.BackupMethod.N4W1). This
makes method validation explicit and raises an AssertionError with a clear
failure mode before using the dict lookup that currently raises a KeyError;
place the assert immediately before the num_screens assignment in the
input_flow_eckhart method.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a039282f-6b92-43a3-9da0-5db463dc2c7d

📥 Commits

Reviewing files that changed from the base of the PR and between b8e841b and 53dcb05.

📒 Files selected for processing (19)
  • .github/workflows/core.yml
  • core/src/apps/management/recovery_device/layout.py
  • core/src/apps/management/reset_device/layout.py
  • core/src/trezor/ui/layouts/eckhart/recovery.py
  • tests/conftest.py
  • tests/device_tests/reset_recovery/test_recovery_bip39_dryrun.py
  • tests/device_tests/reset_recovery/test_recovery_bip39_t2.py
  • tests/device_tests/reset_recovery/test_recovery_slip39_advanced.py
  • tests/device_tests/reset_recovery/test_recovery_slip39_advanced_dryrun.py
  • tests/device_tests/reset_recovery/test_recovery_slip39_basic.py
  • tests/device_tests/reset_recovery/test_recovery_slip39_basic_dryrun.py
  • tests/device_tests/reset_recovery/test_reset_recovery_bip39.py
  • tests/device_tests/reset_recovery/test_reset_recovery_slip39_advanced.py
  • tests/device_tests/reset_recovery/test_reset_recovery_slip39_basic.py
  • tests/device_tests/reset_recovery/test_reset_slip39_basic.py
  • tests/device_tests/test_repeated_backup.py
  • tests/input_flows.py
  • tests/input_flows_helpers.py
  • tests/ui_tests/fixtures.json
✅ Files skipped from review due to trivial changes (3)
  • tests/device_tests/reset_recovery/test_recovery_slip39_advanced_dryrun.py
  • tests/device_tests/reset_recovery/test_recovery_bip39_t2.py
  • tests/device_tests/reset_recovery/test_recovery_slip39_advanced.py
🚧 Files skipped from review as they are similar to previous changes (6)
  • tests/conftest.py
  • tests/device_tests/reset_recovery/test_recovery_slip39_basic_dryrun.py
  • tests/device_tests/reset_recovery/test_reset_recovery_slip39_basic.py
  • tests/device_tests/reset_recovery/test_reset_recovery_bip39.py
  • tests/device_tests/reset_recovery/test_reset_slip39_basic.py
  • tests/device_tests/reset_recovery/test_recovery_slip39_basic.py

Comment on lines +237 to +246
async def intro(self, num_of_words: int | None = None) -> None:
# TODO: design/copy
pass

async def backup(self, iter_shares: Iterable[ShareInfo]) -> None:
# TODO: warn user about safety

# backup all shares
for share in iter_shares:
await self._backup_share(share)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Don’t skip the backup intro/safety screens on the N4W1 path.

intro() is a no-op and backup() jumps straight into writing shares, so this path misses the same pre-export safety acknowledgment that _DisplayBackup shows before exposing the seed.

Possible stopgap
     class _N4W1Backup:
 
         async def intro(self, num_of_words: int | None = None) -> None:
-            # TODO: design/copy
-            pass
+            from trezor.ui.layouts.reset import show_intro_backup
+
+            await show_intro_backup(num_of_words=num_of_words)
 
         async def backup(self, iter_shares: Iterable[ShareInfo]) -> None:
-            # TODO: warn user about safety
+            from trezor.ui.layouts.reset import show_warning_backup
+
+            await show_warning_backup()
 
             # backup all shares
             for share in iter_shares:
                 await self._backup_share(share)

I can help sketch a minimal patch that reuses the existing intro/warning screens until the N4W1-specific copy lands.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@core/src/apps/management/reset_device/layout.py` around lines 237 - 246, The
intro() method is currently a no-op and backup() immediately writes shares,
skipping the pre-export safety screens; implement intro() to reuse the existing
_DisplayBackup intro/warning flow (e.g., await
self._display_backup.intro(num_of_words) or delegate to the _DisplayBackup.intro
method) and ensure backup() calls await self.intro(num_of_words) (or awaits the
same _DisplayBackup warning) before iterating and calling _backup_share(share)
so the N4W1 path shows the same safety/acknowledgement screens as
_DisplayBackup.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (2)
core/src/apps/management/reset_device/layout.py (1)

237-246: ⚠️ Potential issue | 🟠 Major

Reuse the existing backup disclosure flow before N4W1 writes.

intro() is a no-op and backup() starts exporting shares immediately, so the N4W1 path skips the intro/safety acknowledgement that _DisplayBackup shows before exposing mnemonic material.

Possible stopgap
         async def intro(self, num_of_words: int | None = None) -> None:
-            # TODO: design/copy
-            pass
+            from trezor.ui.layouts.reset import show_intro_backup
+
+            await show_intro_backup(num_of_words=num_of_words)

         async def backup(self, iter_shares: Iterable[ShareInfo]) -> None:
-            # TODO: warn user about safety
+            from trezor.ui.layouts.reset import show_warning_backup
+
+            await show_warning_backup()

             # backup all shares
             for share in iter_shares:
                 await self._backup_share(share)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@core/src/apps/management/reset_device/layout.py` around lines 237 - 246, The
intro() method is a no-op and backup() immediately calls _backup_share, which
skips the existing _DisplayBackup disclosure/safety acknowledgement used in the
N4W1 flow; update intro() to present the same disclosure/acknowledgement flow
used by _DisplayBackup (or call the common method/component that shows the
copy/safety notice) and modify backup() to ensure it awaits/uses intro() before
iterating shares (i.e., call await self.intro(...) or invoke the shared
disclosure helper before calling self._backup_share for each ShareInfo) so the
safety intro is always shown prior to exposing mnemonic material.
core/src/apps/management/recovery_device/layout.py (1)

283-291: ⚠️ Potential issue | 🟡 Minor

Don't warn on the default method=None path.

In non-N4W1 builds this still logs Unsupported backup method: None for the ordinary recovery flow. Restrict the warning to explicit non-None unsupported values.

Proposed patch
-        if method is not BackupMethod.Display and __debug__:
+        if method not in (None, BackupMethod.Display) and __debug__:
             from trezor import log

             log.warning(__name__, "Unsupported backup method: %s", method)

Based on learnings, method=None is an intentional placeholder and should not trigger unsupported-method warnings.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@core/src/apps/management/recovery_device/layout.py` around lines 283 - 291,
The warning should not fire for the intentional placeholder value None; update
choose_handler so the log.warning inside the __debug__ block only runs when
method is not None and not BackupMethod.Display. Locate the choose_handler
function (and the log.warning call that references Unsupported backup method)
and change the condition to require method is not None in addition to method is
not BackupMethod.Display so only explicit unsupported enum values trigger the
warning; keep returning _DisplayHandler unchanged.
🧹 Nitpick comments (1)
tests/device_tests/reset_recovery/test_reset_recovery_slip39_basic.py (1)

38-39: Thread backup_method through the reset leg too.

The new fixture only affects the recovery half of this roundtrip. reset(session) still uses the default display backup flow, so the N4W1 matrix here never exercises reset-time backup.

Proposed patch
-def test_reset_recovery(client: Client, backup_method: BackupMethod):
+def test_reset_recovery(client: Client, backup_method: BackupMethod):
     session = client.get_seedless_session()

-    mnemonics = reset(session)
+    mnemonics = reset(session, method=backup_method)
     session = client.get_session()
     address_before = btc.get_address(session, "Bitcoin", parse_path("m/44h/0h/0h/0/0"))
-def reset(session: Session, strength: int = 128) -> list[str]:
+def reset(
+    session: Session,
+    strength: int = 128,
+    method: BackupMethod = BackupMethod.Display,
+) -> list[str]:
     with session.test_ctx as client:
-        IF = InputFlowSlip39BasicResetRecovery(session)
+        IF = InputFlowSlip39BasicResetRecovery(session, method)
         client.set_input_flow(IF.get())

         device.setup(
             session,
             strength=strength,
             passphrase_protection=False,
             pin_protection=False,
             label="test",
             backup_type=BackupType.Slip39_Basic,
+            backup_method=method,
             entropy_check_count=0,
             _get_entropy=MOCK_GET_ENTROPY,
         )

Also applies to: 52-52

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/device_tests/reset_recovery/test_reset_recovery_slip39_basic.py` around
lines 38 - 39, The test only applies the backup_method fixture to the recovery
half; thread the backup_method through the reset leg so reset uses the same
backup flow. Update the test_reset_recovery code paths that call
client.get_seedless_session() and reset(session) to pass the backup_method
fixture into the reset flow (for example by calling reset(session,
backup_method=backup_method) or by creating the session with
client.get_seedless_session(backup_method=backup_method) so both
get_seedless_session and reset() use the same backup_method); do the same change
for the other occurrence referenced (around line 52) so both reset and recovery
phases exercise the selected backup_method.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@core/src/apps/management/recovery_device/layout.py`:
- Around line 283-291: The warning should not fire for the intentional
placeholder value None; update choose_handler so the log.warning inside the
__debug__ block only runs when method is not None and not BackupMethod.Display.
Locate the choose_handler function (and the log.warning call that references
Unsupported backup method) and change the condition to require method is not
None in addition to method is not BackupMethod.Display so only explicit
unsupported enum values trigger the warning; keep returning _DisplayHandler
unchanged.

In `@core/src/apps/management/reset_device/layout.py`:
- Around line 237-246: The intro() method is a no-op and backup() immediately
calls _backup_share, which skips the existing _DisplayBackup disclosure/safety
acknowledgement used in the N4W1 flow; update intro() to present the same
disclosure/acknowledgement flow used by _DisplayBackup (or call the common
method/component that shows the copy/safety notice) and modify backup() to
ensure it awaits/uses intro() before iterating shares (i.e., call await
self.intro(...) or invoke the shared disclosure helper before calling
self._backup_share for each ShareInfo) so the safety intro is always shown prior
to exposing mnemonic material.

---

Nitpick comments:
In `@tests/device_tests/reset_recovery/test_reset_recovery_slip39_basic.py`:
- Around line 38-39: The test only applies the backup_method fixture to the
recovery half; thread the backup_method through the reset leg so reset uses the
same backup flow. Update the test_reset_recovery code paths that call
client.get_seedless_session() and reset(session) to pass the backup_method
fixture into the reset flow (for example by calling reset(session,
backup_method=backup_method) or by creating the session with
client.get_seedless_session(backup_method=backup_method) so both
get_seedless_session and reset() use the same backup_method); do the same change
for the other occurrence referenced (around line 52) so both reset and recovery
phases exercise the selected backup_method.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8e18c8a3-1132-4528-869b-a022ee1ab303

📥 Commits

Reviewing files that changed from the base of the PR and between b8e841b and 53dcb05.

📒 Files selected for processing (19)
  • .github/workflows/core.yml
  • core/src/apps/management/recovery_device/layout.py
  • core/src/apps/management/reset_device/layout.py
  • core/src/trezor/ui/layouts/eckhart/recovery.py
  • tests/conftest.py
  • tests/device_tests/reset_recovery/test_recovery_bip39_dryrun.py
  • tests/device_tests/reset_recovery/test_recovery_bip39_t2.py
  • tests/device_tests/reset_recovery/test_recovery_slip39_advanced.py
  • tests/device_tests/reset_recovery/test_recovery_slip39_advanced_dryrun.py
  • tests/device_tests/reset_recovery/test_recovery_slip39_basic.py
  • tests/device_tests/reset_recovery/test_recovery_slip39_basic_dryrun.py
  • tests/device_tests/reset_recovery/test_reset_recovery_bip39.py
  • tests/device_tests/reset_recovery/test_reset_recovery_slip39_advanced.py
  • tests/device_tests/reset_recovery/test_reset_recovery_slip39_basic.py
  • tests/device_tests/reset_recovery/test_reset_slip39_basic.py
  • tests/device_tests/test_repeated_backup.py
  • tests/input_flows.py
  • tests/input_flows_helpers.py
  • tests/ui_tests/fixtures.json
✅ Files skipped from review due to trivial changes (4)
  • tests/device_tests/reset_recovery/test_recovery_bip39_t2.py
  • tests/device_tests/reset_recovery/test_recovery_bip39_dryrun.py
  • tests/device_tests/reset_recovery/test_reset_recovery_slip39_advanced.py
  • tests/device_tests/reset_recovery/test_recovery_slip39_advanced.py
🚧 Files skipped from review as they are similar to previous changes (6)
  • tests/device_tests/reset_recovery/test_recovery_slip39_advanced_dryrun.py
  • tests/device_tests/reset_recovery/test_reset_recovery_bip39.py
  • tests/device_tests/reset_recovery/test_recovery_slip39_basic_dryrun.py
  • tests/input_flows_helpers.py
  • tests/conftest.py
  • tests/input_flows.py

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

translations Put this label on a PR to run tests in all languages

Projects

Status: 🔎 Needs review

Development

Successfully merging this pull request may close these issues.

1 participant